from . import ajax


class Column:
    def __init__(self, table, column_type, column_name):
        self.table = table
        self.column_type = column_type
        self.column_name = column_name
        self.column_data = None

    def __len__(self, index):
        if self.column_data is None:
            self.table._read()
        return len(self.column_data)

    def __getitem__(self, index):
        if self.column_data is None:
            self.table._read()
        assert isinstance(index, int)
        return self.column_data[index]

    def __eq__(self, val):
        return EQ(self, val)

    def __gt__(self, val):
        return GT(self, val)

    def __ne__(self, val):
        return NE(self, val)

    def __lt__(self, val):
        return LT(self, val)

    def __le__(self, val):
        return LE(self, val)

    def __ge__(self, val):
        return GE(self, val)


class MAX:
    def __init__(self, table):
        self.dest_tables = table


class MIN:
    def __init__(self, table):
        self.dest_tables = table


class Table:
    def __init__(self, transaction, table_name, column_types, column_names):
        self.transaction = transaction
        self.table_name = table_name
        self.columns = []
        assert len(column_types) == len(column_names)
        for t, n in zip(column_types, column_names):
            c = Column(self, t, n)
            self.columns.append(c)
            setattr(self, n, c)

    def _setdata(self, rows):
        if len(rows) > 0:
            col_data = list(zip(*rows))
        else:
            col_data = [[] for _ in self.columns]
        for i, colomn in enumerate(self.columns):
            colomn.column_data = col_data[i]

    def _read(self):
        table_data = self.transaction._read_table(self.table_name)
        self._setdata(table_data['rows'])

    def __len__(self):
        return len(self.columns[0])

    def __getitem__(self, index):
        assert isinstance(index, int)
        return [c[index] for c in self.columns]

    def get_column(self, column_name):
        for col in self.columns:
            if col.column_name == column_name:
                return col

    def map_from(self, table, data, agg):
        if self is table:
            return data
        schema = self.transaction.schema
        links = []
        for tb1, tb2 in [[self, table], [table, self]]:
            for fk, (tb, pk) in schema[tb1.table_name]['foreign_keys'].items():
                if tb == tb2.table_name:
                    links.append([tb1.table_name, fk, tb, pk])
        assert len(links) == 1, '表格之间存在不唯一的路径：'+str(links)
        tb1, fk, tb2, pk = links[0]

        def _index_map(col_fk, col_pk):
            v2i = {val: i for i, val in enumerate(col_pk.column_data)}
            return {i: v2i.get(val, None) for i, val in enumerate(col_fk.column_data)}
        if tb1 == self.table_name:
            col_fk = self.get_column(fk)
            col_pk = table.get_column(pk)
            index_map = _index_map(col_fk, col_pk)
            res = [agg(None, None) for v in col_fk.column_data]
            for i, j in index_map.items():
                if j is not None:
                    res[i] = data[j]
        else:
            col_fk = table.get_column(fk)
            col_pk = self.get_column(pk)
            index_map = _index_map(col_fk, col_pk)
            default_val = None
            res = [default_val for v in col_pk.column_data]
            for i, j in index_map.items():
                res[j] = agg(res[j], data[i])
        return res


class OR:
    def __init__(self, *ops):
        for op in ops:
            assert isLegalOp(op)
        self.ops = ops

    def eval(self, dest_table):
        def agg(val1, val2):
            if val1 is None:
                if val2 is None:
                    return False
                return val2
            return val1 or val2
        vals = [dest_table.map_from(op.left.table, op.eval(), agg)
                for op in self.ops]
        # 用 max 来表示 OR
        t = [max(*r) for r in zip(*vals)]
        return t


class AND:
    def __init__(self, *ops):
        for op in ops:
            assert isLegalOp(op)
        self.ops = ops

    def eval(self, dest_table):
        def agg(val1, val2):
            if val1 is None:
                if val2 is None:
                    return False
                return val2
            return val1 or val2

        assert isinstance(dest_table, Table)

        vals = [dest_table.map_from(op.left.table, op.eval(), agg)
                for op in self.ops]
        # min 表示 AND 操作
        # [[true, false], [true, false]]
        t = [min(*r) for r in zip(*vals)]
        return t


def isLegalOp(op):
    return isinstance(op, (EQ, GT, NE, LT, GE, LE))


class EQ:
    def __init__(self, left, right):
        assert isinstance(left, Column)
        assert isinstance(right, (int, float, str, bool))
        self.left = left
        self.right = right

    def eval(self):
        return [self.left[i] == self.right for i in range(len(self.left))]


class GT:
    def __init__(self, left, right):
        assert isinstance(left, Column)
        assert isinstance(right, (int, float, str, bool))
        self.left = left
        self.right = right

    def eval(self):
        return [self.left[i] > self.right for i in range(len(self.left))]


class NE:
    def __init__(self, left, right):
        assert isinstance(left, Column)
        assert isinstance(right, (int, float, str, bool))
        self.left = left
        self.right = right

    def eval(self):
        return [self.left[i] != self.right for i in range(len(self.left))]


class LT:
    def __init__(self, left, right):
        assert isinstance(left, Column)
        assert isinstance(right, (int, float, str, bool))
        self.left = left
        self.right = right

    def eval(self):
        return [self.left[i] < self.right for i in range(len(self.left))]


class GE:
    def __init__(self, left, right):
        assert isinstance(left, Column)
        assert isinstance(right, (int, float, str, bool))
        self.left = left
        self.right = right

    def eval(self):
        return [self.left[i] >= self.right for i in range(len(self.left))]


class LE:
    def __init__(self, left, right):
        assert isinstance(left, Column)
        assert isinstance(right, (int, float, str, bool))
        self.left = left
        self.right = right

    def eval(self):
        return [self.left[i] <= self.right for i in range(len(self.left))]


class transaction:
    def __init__(self, db_id, user_secrets, table_users=None):

        # 后端交互，创建一个事务
        self.transaction_id = ajax.rpc.create_transaction(
            user_secrets, db_id, table_users)
        self.db_id = db_id
        self.user_secrets = user_secrets
        self.table_users = table_users
        self.tables = []

    def __enter__(self):
        self.schema = self._get_db_schema()
        for table_name, table_schema in self.schema.items():
            t = Table(self, table_name,
                      table_schema['field_types'],
                      table_schema['field_names'])
            # magic: self.table_name = t
            setattr(self, table_name, t)
            self.tables.append(t)
        return self

    def __exit__(self, ex_type, ex, ex_tb):
        print("******exit!")
        if self.transaction_id is None:
            print("no id")
            return
        if ex is None:
            print("commit")
            ajax.rpc.commit_transaction(self.user_secrets,
                                        self.transaction_id)
        else:
            print("error")
            ajax.rpc.abort_transaction(self.user_secrets,
                                       self.transaction_id)
            raise ex

    def _get_db_schema(self):
        # 和后端交互，打开一个数据库
        return ajax.rpc.get_db_schema(self.user_secrets,
                                      self.transaction_id,
                                      self.db_id)

    def _read_table(self, table_name):
        return ajax.rpc.read_table(self.user_secrets,
                                   self.transaction_id,
                                   self.db_id,
                                   table_name)

    def _write_table(self, table_name, table_data):
        return ajax.rpc.write_table(self.user_secrets,
                                    self.transaction_id,
                                    self.db_id,
                                    table_name, table_data)

    def INSERT(self, insertInto=None, values=None):
        '''
            insertInto: str, table_name
            values: list
        '''
        assert isinstance(insertInto, Table), "insert Into a invaliad table"
        table_name = insertInto.table_name
        # assert len(values) == len(insertInto.columns), "wanted col len is: {0} but {1} come".format(
        #     len(insertInto.columns), len(values))
        try:
            ajax.rpc.insert(self.user_secrets, self.transaction_id,
                            self.db_id, table_name, values)
        except:
            print("err")

        return True

    def doWhere(self, where, table):
        src_tables = {}
        # assert where is not None, "where is none"
        # where 是一个运算类
        assert isinstance(where, (AND, OR))
        for op in where.ops:
            if isLegalOp(op):
                assert isinstance(op.left, Column)
                src_tables[op.left.table.table_name] = op.left.table
        w = where.eval(table)
        return w

    def SQL(self, select=None, where=None, insertInto=None, values=None, update=None, set=None, delete=None):

        if insertInto != None:
            self.INSERT(insertInto, values)
            return

        if update != None:
            self.UPDATE(update, set, where)
            return
        if delete != None:
            self.DELETE(delete, where)
            return

        dest_tables = {}
        src_tables = {}
        isMax = isinstance(select, MAX)
        isMin = isinstance(select, MIN)
        if select is not None:
            if isinstance(select, (MAX, MIN)):
                select = select.dest_tables
            for column in select:
                assert isinstance(column, (Column,))
                # 目标列？
                dest_tables[column.table.table_name] = column.table
        if where is not None:
            # where 是一个运算类
            assert isinstance(where, (AND, OR))
            for op in where.ops:
                if isLegalOp(op):
                    assert isinstance(op.left, Column)
                    src_tables[op.left.table.table_name] = op.left.table

        table = list(dest_tables.keys())[0]
        table = dest_tables[table]
        table._read()
        for table_name in src_tables:
            src_table = src_tables[table_name]
            src_table._read()

        # 这里才执行条件运算
        # 实际上就是把表格按行过滤
        w = []
        if where is not None:
            w = where.eval(table)

        rows = []
        for i in range(len(table)):
            if (where is not None) and (not w[i]):
                continue
             # 处理 min 和 max
            if isMax:
                if len(rows) == 0:
                    rows.append([column[i] for column in select])
                else:
                    for idx, column in enumerate(select):
                        # print("column ", column)
                        if rows[0][idx] < column[i]:
                            rows[0][idx] = column[i]
            elif isMin:
                if len(rows) == 0:
                    rows.append([column[i] for column in select])
                else:
                    for idx, column in enumerate(select):
                        if rows[0][idx] > column[i]:
                            rows[0][idx] = column[i]

            else:
                rows.append([column[i] for column in select])

        if isMax:
            res_table = Table(table.transaction,
                              '查询结果',
                              [c.column_type for c in select],
                              [c.column_name + "_max" for c in select])
        elif isMin:
            res_table = Table(table.transaction,
                              '查询结果',
                              [c.column_type for c in select],
                              [c.column_name + "_min" for c in select])
        else:
            res_table = Table(table.transaction,
                              '查询结果',
                              [c.column_type for c in select],
                              [c.column_name for c in select])
        res_table._setdata(rows)

        return res_table

    def UPDATE(self, updateTable, set, where):
        # 根据 where 返回结果
        # 修改 updateTable 里对应的 set 内容
        # 然后写入数据库
        print("ok")
        assert isinstance(updateTable, Table), "is not table"
        print("no")
        updateTable._read()
        w = self.doWhere(where, updateTable)

        table = updateTable.columns
        rows = []
        for i in range(len(table[0])):
            if not w[i]:
                rows.append([column[i] for column in table])
            else:
                t = []
                for column in table:
                    assert isinstance(set[0], Column), "is not cloum"

                    if column.column_name == set[0].column_name:
                        t.append(set[1])
                    else:
                        t.append(column[i])
                rows.append(t)

        ajax.rpc.update(self.user_secrets, self.transaction_id,
                        self.db_id, updateTable.table_name, rows)

    def DELETE(self, deleteTable, where):
        assert isinstance(deleteTable, Table), "is not table"
        print("no")
        deleteTable._read()
        w = self.doWhere(where, deleteTable)

        table = deleteTable.columns
        rows = []
        for i in range(len(table[0])):
            if not w[i]:
                rows.append([column[i] for column in table])
        ajax.rpc.delete(self.user_secrets, self.transaction_id,
                        self.db_id, deleteTable.table_name, rows)

    def CREATE(self, table=None, database=None):
        pass
